<?php
namespace Avalanche;

define('CRLF', "\r\n");

/**
 * Avalanche is non-blocking framework system
 *
 * @author Vitold Sedyshev
 */
class Avalanche
{

    /**
     * Server bind on host
     *
     * @var string
     */
    protected $host = '0.0.0.0';

    /**
     * Server bind on port
     *
     * @var int
     */
    protected $port = 8888;

    /**
     * @var null
     */
    protected $handleAcceptConnection;

    /**
     * @var null
     */
    protected $handleReadConnection;

    /**
     * @var null
     */
    protected $handleWriteConnection;

    /**
     * @var null
     */
    protected $handleCloseConnection;

    /**
     * @param string $className
     */
    public function autoload($className) {
        $path = dirname(__FILE__) . '/../' . str_replace('\\', '/', $className) . '.php';
        echo 'Autolod ' . $path . PHP_EOL;
        if (file_exists($path)) {
            require_once $path;
        }
    }

    /**
     * Initialize system Avalanche
     *
     * @return void
     */
    public function init()
    {
        spl_autoload_register(array($this, 'autoload'));
    }

    /**
     * Handle new connection
     *
     * @param callable $handleConnection
     */
    public function setHandleAcceptConnection($handleConnection) {
        $this->handleAcceptConnection = $handleConnection;
    }

    /**
     * @param string $msg
     */
    protected function log($msg) {
        // ...
    }

    /**
     * @var array
     */
    protected $clients = array();

    /**
     * Start session
     *
     * @param Model\ClientModel $client
     */
    protected function startSession(\Avalanche\Model\ClientModel $client) {
        $clientId = (int)$client->getSock();
        $this->clients[$clientId] = $client;
    }

    /**
     * Stop session
     *
     * @param Model\ClientModel $client
     */
    protected function stopSession(\Avalanche\Model\ClientModel $client) {
        $sessionId = (int)$client->getSock();
        $client = $this->clients[$sessionId];
        $client->close();
        $this->clients[$sessionId] = null;
        unset($this->clients[$sessionId]);
    }

    /**
     * Handle connection
     *
     * @param resource $sock
     * @param resource $connection
     */
    public function handleAcceptConnection($sock, $connection)
    {
        $this->log('Client ' . $connection . ' has connected');

        // Create client connection
        $client = new \Avalanche\Model\ClientModel();
        $client->setSock($connection);

        // Add client to dict with connections
        $this->startSession($client);

        // Call user handler
        if (is_callable($this->handleAcceptConnection)) {
            call_user_func_array($this->handleAcceptConnection, array($client));
        }
    }

    /**
     * Serve connection
     *
     * @return void
     */
    public function serve()
    {
        $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_bind($socket, $this->host, $this->port);
        socket_listen($socket);
        socket_set_nonblock($socket);

        // Infinite connections serve
        while (true) {

            // Generate waiting sockets
            $readConnections = array();
            $readConnections[] = $socket;
            foreach ($this->clients as $session) {
                $readConnections[] = $session->getSock();
            }

            //$readConnections = array();
            $writeConnections = array();
            $exceptConnections = $readConnections;

            // Wait connections in select module
            if ($readConnections !== array()) {

                $socs = socket_select($readConnections, $writeConnections, $exceptConnections, null);
                if ($socs !== false) {

                    // Reading connection handle
                    foreach ($readConnections as $readConnection) {
                        //var_dump($readConnection);

                        // Accept new server connection
                        if ($readConnection === $socket) {
                            $connection = @socket_accept($socket);
                            if ($connection !== false) {
                                $this->handleAcceptConnection($readConnection, $connection);
                            }
                        } else {
                            $handled = $this->handleReadConnection($readConnection);
                            if ($handled) {
                                $connectionId = (int)$readConnection;
                                $client = $this->getSessionById($connectionId);
                                $this->stopSession($client);

                                $this->handleCloseConnection($connection);
                            }
                        }


                    }

                    // Write connection handle

                    // Except connection handle
                    foreach ($exceptConnections as $exceptConnection) {
                        echo 'Except: ' . $exceptConnection . PHP_EOL;
                    }
                }
            }

        }

    }

    /**
     * Handle read connection
     *
     * @param resource $connection
     * @return bool
     */
    protected function handleReadConnection($connection) {
        $result = true;

        // Get session by ID
        $connectionId = (int)$connection;
        $session = $this->getSessionById($connectionId);

        // Read connection
        $requestBuffer = socket_read($connection, 2048);

        // Append request buffer
        $session->writeRequestBuffer($requestBuffer);

        // Try to handle buffer
        if (is_callable($this->handleReadConnection)) {
            $result = call_user_func_array($this->handleReadConnection, array($session));
        }

        return $result;
    }

    /**
     * @param int $connectionId
     * @return null|\Avalanche\Model\ClientModel
     */
    private function getSessionById($connectionId)
    {
        $result = null;
        if (array_key_exists($connectionId, $this->clients)) {
            $result = $this->clients[$connectionId];
        }
        return $result;
    }

    /**
     * @param callable $handleReadConnection
     */
    public function setHandleReadConnection($handleReadConnection)
    {
        $this->handleReadConnection = $handleReadConnection;
    }

    /**
     * @param callable $handleCloseConnection
     */
    public function setHandleCloseConnection($handleCloseConnection) {
        $this->handleCloseConnection = $handleCloseConnection;
    }

    /**
     * @param resource $connection
     */
    protected function handleCloseConnection($connection)
    {
        // Get session by ID
        $connectionId = (int)$connection;
        $client = $this->getSessionById($connectionId);

        // Handle
        if (is_callable($this->handleCloseConnection)) {
            call_user_func_array($this->handleCloseConnection, array($client));
        }
    }

}
